// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MarketBook.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.Linq;

    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;

    #endregion

    /// <summary>
    /// The market book.
    /// </summary>
    [Serializable]
    public class MarketBook : IMarketBook
    {
        #region Constants and Fields

        /// <summary>
        /// The asks.
        /// </summary>
        private MarketBookCell[] asks = new MarketBookCell[10];

        /// <summary>
        /// The bids.
        /// </summary>
        private MarketBookCell[] bids = new MarketBookCell[10];

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="MarketBook"/> class.
        /// </summary>
        /// <param name="depth">
        /// The depth.
        /// </param>
        public MarketBook(short depth)
        {
            this.Initialize(depth);
        }

        #endregion

        #region Properties

        /// <summary>
        /// The ask depth.
        /// </summary>
        /// <returns>
        /// The ask depth.
        /// </returns>
        public short AskDepth
        {
            get
            {
                for (var d = (short)(this.Depth - 1); d >= 0; d--)
                {
                    if (this.Asks[d].HasValue)
                    {
                        if (this.Asks[d].Price > Constants.Zero && this.Asks[d].Size > 0)
                        {
                            return d;
                        }
                    }
                }

                return -1;
            }
        }

        /// <summary>
        /// Gets Asks.
        /// </summary>
        public MarketBookCell[] Asks
        {
            get
            {
                return this.asks;
            }
        }

        /// <summary>
        /// The bid depth.
        /// </summary>
        /// <returns>
        /// The bid depth.
        /// </returns>
        public short BidDepth
        {
            get
            {
                for (var d = (short)(this.Depth - 1); d >= 0; d--)
                {
                    if (this.Bids[d].HasValue)
                    {
                        if (this.Bids[d].Price > Constants.Zero && this.Bids[d].Size > 0)
                        {
                            return d;
                        }
                    }
                }

                return -1;
            }
        }

        /// <summary>
        /// Gets Bids.
        /// </summary>
        public MarketBookCell[] Bids
        {
            get
            {
                return this.bids;
            }
        }

        /// <summary>
        /// Gets or sets Depth.
        /// </summary>
        public short Depth { get; set; }

        #endregion

        #region Implemented Interfaces

        #region IMarketBook

        /// <summary>
        /// The ask limit.
        /// </summary>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <returns>
        /// The ask limit.
        /// </returns>
        public int AskLimit(double price)
        {
            if (price < Constants.Zero)
            {
                return -1;
            }

            for (var d = 0; d < this.Depth; d++)
            {
                if (this.Asks[d].HasValue)
                {
                    if (price >= this.Asks[d].Price)
                    {
                        return d;
                    }
                }
            }

            return -1;
        }

        /// <summary>
        /// The ask price.
        /// </summary>
        /// <param name="size">
        /// The size.
        /// </param>
        /// <returns>
        /// The ask price.
        /// </returns>
        public double AskPrice(int size)
        {
            if (size <= 0)
            {
                return 0.0;
            }

            var ps = 0.0;
            var s = 0;

            for (var d = 0; d < this.Depth; d++)
            {
                if (this.Asks[d].HasValue)
                {
                    if (size <= this.Asks[d].Size)
                    {
                        return (ps + (size * this.Asks[d].Price)) / (s + size);
                    }

                    ps += this.Asks[d].Size * this.Asks[d].Price;
                    s += this.Asks[d].Size;
                    size -= this.Asks[d].Size;
                }
            }

            return 0.0;
        }

        /// <summary>
        /// The ask price.
        /// </summary>
        /// <param name="size">
        /// The size.
        /// </param>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <param name="depth">
        /// The depth.
        /// </param>
        /// <param name="nbexec">
        /// The nbexec.
        /// </param>
        /// <param name="tottrd">
        /// The tottrd.
        /// </param>
        /// <returns>
        /// The ask price.
        /// </returns>
        public bool AskPrice(int size, ref double price, ref int depth, ref int nbexec, ref double tottrd)
        {
            var ps = 0.0;
            var s = 0;
            depth = 0;
            price = 0.0;
            nbexec = 0;
            tottrd = 0.0;

            if (size <= 0)
            {
                return false;
            }

            for (var d = 0; d < this.Depth; d++)
            {
                if (this.Asks[d].HasValue)
                {
                    if (size <= this.Asks[d].Size)
                    {
                        nbexec += this.Asks[d].Contributions.Count();
                        ps += size * this.Asks[d].Price;
                        price = ps / (s + size);
                        tottrd = ps;
                        return true;
                    }

                    ps += this.Asks[d].Size * this.Asks[d].Price;
                    s += this.Asks[d].Size;
                    size -= this.Asks[d].Size;
                    depth++;
                    nbexec += this.Asks[d].Contributions.Count();
                }
            }

            return false;
        }

        /// <summary>
        /// The ask price nominal.
        /// </summary>
        /// <param name="nominal">
        /// The nominal.
        /// </param>
        /// <returns>
        /// The ask price nominal.
        /// </returns>
        public double AskPriceNominal(double nominal)
        {
            if (this.Depth <= 0)
            {
                return 0.0;
            }

            if (!this.Asks[0].HasValue)
            {
                return 0.0;
            }

            if (this.Asks[0].Price < Constants.Zero)
            {
                return 0.0;
            }

            return this.AskPrice((int)(nominal / this.Asks[0].Price));
        }

        /// <summary>
        /// The bid limit.
        /// </summary>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <returns>
        /// The bid limit.
        /// </returns>
        public int BidLimit(double price)
        {
            if (price < Constants.Zero)
            {
                return -1;
            }

            for (var d = 0; d < this.Depth; d++)
            {
                if (this.Bids[d].HasValue)
                {
                    if (price >= this.Bids[d].Price)
                    {
                        return d;
                    }
                }
            }

            return -1;
        }

        /// <summary>
        /// The bid price.
        /// </summary>
        /// <param name="size">
        /// The size.
        /// </param>
        /// <returns>
        /// The bid price.
        /// </returns>
        public double BidPrice(int size)
        {
            if (size <= 0)
            {
                return 0.0;
            }

            var ps = 0.0;
            var s = 0;

            for (var d = 0; d < this.Depth; d++)
            {
                if (this.Bids[d].HasValue)
                {
                    if (size <= this.Bids[d].Size)
                    {
                        return (ps + (size * this.Bids[d].Price)) / (s + size);
                    }

                    ps += this.Bids[d].Size * this.Bids[d].Price;
                    s += this.Bids[d].Size;
                    size -= this.Bids[d].Size;
                }
            }

            return 0.0;
        }

        /// <summary>
        /// The bid price.
        /// </summary>
        /// <param name="size">
        /// The size.
        /// </param>
        /// <param name="price">
        /// The price.
        /// </param>
        /// <param name="depth">
        /// The depth.
        /// </param>
        /// <param name="nbexec">
        /// The nbexec.
        /// </param>
        /// <param name="tottrd">
        /// The tottrd.
        /// </param>
        /// <returns>
        /// The bid price.
        /// </returns>
        public bool BidPrice(int size, ref double price, ref int depth, ref int nbexec, ref double tottrd)
        {
            var ps = 0.0;
            var s = 0;
            depth = 0;
            price = 0.0;
            nbexec = 0;
            tottrd = 0.0;

            if (size <= 0)
            {
                return false;
            }

            for (var d = 0; d < this.Depth; d++)
            {
                if (this.Bids[d].HasValue)
                {
                    if (size <= this.Bids[d].Size)
                    {
                        nbexec += this.Bids[d].Contributions.Count();
                        ps += size * this.Bids[d].Price;
                        price = ps / (s + size);
                        tottrd = ps;
                        return true;
                    }

                    ps += this.Bids[d].Size * this.Bids[d].Price;
                    s += this.Bids[d].Size;
                    size -= this.Bids[d].Size;
                    depth++;
                    nbexec += this.Bids[d].Contributions.Count();
                }
            }

            return false;
        }

        /// <summary>
        /// The bid price nominal.
        /// </summary>
        /// <param name="nominal">
        /// The nominal.
        /// </param>
        /// <returns>
        /// The bid price nominal.
        /// </returns>
        public double BidPriceNominal(double nominal)
        {
            if (this.Depth <= 0)
            {
                return 0.0;
            }

            if (!this.Bids[0].HasValue)
            {
                return 0.0;
            }

            if (this.Bids[0].Price < Constants.Zero)
            {
                return 0.0;
            }

            return this.BidPrice((int)(nominal / this.Bids[0].Price));
        }

        /// <summary>
        /// The get snapshot.
        /// </summary>
        /// <returns>
        /// </returns>
        public FeedEventArgs.MarketBookUpdate GetSnapshot()
        {
            IList<MarketBookCell> bookCells = new List<MarketBookCell>();

            for (var d = (short)0; d < this.Depth; d++)
            {
                var c = this.bids[d];
                bookCells.Add(MarketBookCell.Bid(d, Timings.Now, c.Price, c.Size, c.Contributions));
            }

            for (var d = (short)0; d < this.Depth; d++)
            {
                var c = this.asks[d];
                bookCells.Add(MarketBookCell.Ask(d, Timings.Now, c.Price, c.Size, c.Contributions));
            }

            return new FeedEventArgs.MarketBookUpdate(Guid.Empty, bookCells.ToArray(), true);
        }

        /// <summary>
        /// The handle update.
        /// </summary>
        /// <param name="update">
        /// The update.
        /// </param>
        public void HandleUpdate(FeedEventArgs.MarketBookUpdate update)
        {
            // In case of a snapshot, clear past values
            if (update.IsSnapshot)
            {
                this.Initialize(this.Depth);
            }

            foreach (var cell in update.Cells)
            {
                if (cell.Depth >= this.Depth)
                {
                    continue;
                }

                switch (cell.Side)
                {
                    case ESide.Bid:
                        this.bids[cell.Depth] = cell;
                        break;

                    case ESide.Ask:
                        this.asks[cell.Depth] = cell;
                        break;
                    default:
                        return;
                }
            }
        }

        /// <summary>
        /// The mid price.
        /// </summary>
        /// <param name="depth">
        /// The depth.
        /// </param>
        /// <returns>
        /// The mid price.
        /// </returns>
        public double MidPrice(int depth)
        {
            if (depth >= this.Depth)
            {
                return 0.0;
            }

            if (depth < 0)
            {
                return 0.0;
            }

            if (!this.Bids[depth].HasValue || !this.Asks[depth].HasValue)
            {
                return 0.0;
            }

            if (this.Bids[depth].Price < Constants.Zero || this.Asks[depth].Price < Constants.Zero)
            {
                return 0.0;
            }

            return 0.5 * (this.Bids[depth].Price + this.Asks[depth].Price);
        }

        /// <summary>
        /// The mid price.
        /// </summary>
        /// <returns>
        /// The mid price.
        /// </returns>
        public double MidPrice()
        {
            return this.MidPrice(0);
        }

        /// <summary>
        /// The spread.
        /// </summary>
        /// <param name="depth">
        /// The depth.
        /// </param>
        /// <returns>
        /// The spread.
        /// </returns>
        public double Spread(int depth)
        {
            if (depth >= this.Depth)
            {
                return 0.0;
            }

            if (depth < 0)
            {
                return 0.0;
            }

            if (!this.Bids[depth].HasValue || !this.Asks[depth].HasValue)
            {
                return 0.0;
            }

            if (this.Bids[depth].Price < Constants.Zero || this.Asks[depth].Price < Constants.Zero)
            {
                return 0.0;
            }

            return this.Asks[depth].Price - this.Bids[depth].Price;
        }

        /// <summary>
        /// The spread.
        /// </summary>
        /// <returns>
        /// The spread.
        /// </returns>
        public double Spread()
        {
            return this.Spread(0);
        }

        /// <summary>
        /// The spread.
        /// </summary>
        /// <param name="depth">
        /// The depth.
        /// </param>
        /// <param name="spread">
        /// The spread.
        /// </param>
        /// <returns>
        /// The spread.
        /// </returns>
        public bool Spread(int depth, ref double spread)
        {
            if (depth >= this.Depth)
            {
                spread = 0.0;
                return false;
            }

            if (depth < 0)
            {
                spread = 0.0;
                return false;
            }

            if (!this.Bids[depth].HasValue || !this.Asks[depth].HasValue)
            {
                spread = 0.0;
                return false;
            }

            if (this.Bids[depth].Price < Constants.Zero || this.Asks[depth].Price < Constants.Zero)
            {
                spread = 0.0;
                return false;
            }

            spread = this.Asks[depth].Price - this.Bids[depth].Price;

            return spread >= 0;
        }

        /// <summary>
        /// The spread.
        /// </summary>
        /// <param name="spread">
        /// The spread.
        /// </param>
        /// <returns>
        /// The spread.
        /// </returns>
        public bool Spread(ref double spread)
        {
            return this.Spread(ref spread);
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The initialize.
        /// </summary>
        /// <param name="depth">
        /// The depth.
        /// </param>
        private void Initialize(short depth)
        {
            // Instrument = i;
            this.Depth = depth;
            this.bids = new MarketBookCell[depth];
            this.asks = new MarketBookCell[depth];

            for (var d = 0; d < depth; d++)
            {
                this.bids[d] = new MarketBookCell();
                this.asks[d] = new MarketBookCell();
            }
        }

        #endregion
    }
}